{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "73a576e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'B-NAME': 1, 'I-ORG': 2, 'B-PRO': 3, 'B-EDU': 4, 'I-NAME': 5, 'B-LOC': 6, 'B-TITLE': 7, 'B-RACE': 8, 'I-TITLE': 9, 'I-RACE': 10, 'I-PRO': 11, 'B-CONT': 12, 'I-EDU': 13, 'I-LOC': 14, 'I-CONT': 15, 'B-ORG': 16, 'O': 0}\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import json\n",
    "from collections import defaultdict\n",
    "\n",
    "\n",
    "class NERDataset:\n",
    "    def __init__(self):\n",
    "        train_file, test_file, label_file = 'ner_train.jsonl',\\\n",
    "            'ner_test.jsonl', 'ner_labels.json'\n",
    "        \n",
    "        def read_file(file_name):\n",
    "            with open(file_name, 'r', encoding='utf-8') as fin:\n",
    "                json_list = list(fin)\n",
    "            data_split = []\n",
    "            for json_str in json_list:\n",
    "                raw = json.loads(json_str)\n",
    "                d = {'tokens': raw['tokens'], 'tags': raw['tags']}\n",
    "                data_split.append(d)\n",
    "            return data_split\n",
    "        \n",
    "        # 读取JSON文件，转化为Python对象\n",
    "        self.train_data, self.test_data = read_file(train_file),\\\n",
    "            read_file(test_file)\n",
    "        self.label2id = json.loads(open(label_file, 'r').read())\n",
    "        self.id2label = {}\n",
    "        for k, v in self.label2id.items():\n",
    "            self.id2label[v] = k\n",
    "        print(self.label2id)\n",
    "\n",
    "    # 建立词表，过滤低频词\n",
    "    def build_vocab(self, min_freq=3):\n",
    "        # 统计词频\n",
    "        frequency = defaultdict(int)\n",
    "        for data in self.train_data:\n",
    "            tokens = data['tokens']\n",
    "            for token in tokens:\n",
    "                frequency[token] += 1\n",
    "                \n",
    "        print(f'unique tokens = {len(frequency)}, '+\\\n",
    "              f'total counts = {sum(frequency.values())}, '+\\\n",
    "              f'max freq = {max(frequency.values())}, '+\\\n",
    "              f'min freq = {min(frequency.values())}')\n",
    "        \n",
    "        # 由于词与标签一一对应，不能随便删除字符，\n",
    "        # 因此加入<unk>用于替代未登录词，加入<pad>用于批量训练\n",
    "        self.token2id = {'<pad>': 0, '<unk>': 1}\n",
    "        self.id2token = {0: '<pad>', 1: '<unk>'}\n",
    "        total_count = 0\n",
    "        # 将高频词加入词表\n",
    "        for token, freq in sorted(frequency.items(),\\\n",
    "                key=lambda x: -x[1]):\n",
    "            if freq > min_freq:\n",
    "                self.token2id[token] = len(self.token2id)\n",
    "                self.id2token[len(self.id2token)] = token\n",
    "                total_count += freq\n",
    "            else:\n",
    "                break\n",
    "        print(f'min_freq = {min_freq}, '\n",
    "              f'remaining tokens = {len(self.token2id)}, '\n",
    "              f'in-vocab rate = {total_count / sum(frequency.values())}')\n",
    "\n",
    "    # 将文本输入转化为词表中对应的索引\n",
    "    def convert_tokens_to_ids(self):\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in data_split:\n",
    "                data['token_ids'] = []\n",
    "                for token in data['tokens']:\n",
    "                    data['token_ids'].append(self.token2id.get(token, 1)) \n",
    "        \n",
    "dataset = NERDataset()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e45d33a0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unique tokens = 2454, total counts = 182068, max freq = 5630, min freq = 1\n",
      "min_freq = 0, remaining tokens = 2456, in-vocab rate = 1.0\n",
      "[595, 510, 353, 263, 424, 225, 764, 637, 353, 65, 80, 47, 41, 74, 53, 41, 141, 23, 74, 7, 259, 27, 135, 47, 31, 74, 67, 25, 1605, 815, 1157, 225, 595, 1158, 738, 688, 353, 1025, 65, 234, 27, 135, 316, 41, 31, 7, 80, 41, 42, 31, 47, 27, 528, 41, 141, 27, 135, 67, 1005, 1606, 363, 19, 677, 294, 37, 678, 1756, 604, 873, 1006, 1005, 353, 33, 30, 9, 113, 5, 45, 11, 82, 60, 21, 26, 5, 186, 153, 100, 565, 873, 353, 424, 1993, 387, 346, 250, 13, 5, 102, 214, 25, 11, 82, 60, 21, 26, 5, 186, 2, 7, 459, 97, 89, 147, 140, 139, 69, 46, 21, 12, 2, 7, 964, 98, 240, 677, 59, 9, 103, 11, 82, 60, 335, 200, 76, 26, 6, 110, 20, 9, 15, 4, 122, 539, 241, 99, 621, 40, 200, 57, 82, 156, 25, 11, 82, 381, 371, 91, 202, 6, 52, 2, 7, 670, 100, 84, 204, 53, 120, 27, 42, 3, 56, 1159, 3, 329, 31, 265, 31, 3, 56, 394, 394, 3, 84, 357, 84, 7, 25, 397, 7, 41, 7, 74, 7, 135, 7, 31, 7, 87, 7, 259, 7, 31, 7, 74, 7, 41, 7, 131, 7, 28, 289, 385, 4]\n",
      "['阿', '里', '斯', '提', '德', '·', '波', '拉', '斯', '（', 'A', 'r', 'i', 's', 't', 'i', 'd', 'e', 's', ' ', 'B', 'o', 'u', 'r', 'a', 's', '）', '和', '卢', '卡', '雅', '·', '阿', '伊', '纳', '罗', '斯', '托', '（', 'L', 'o', 'u', 'k', 'i', 'a', ' ', 'A', 'i', 'n', 'a', 'r', 'o', 'z', 'i', 'd', 'o', 'u', '）', '夫', '妇', '二', '人', '均', '拥', '有', '希', '腊', '比', '雷', '埃', '夫', '斯', '技', '术', '教', '育', '学', '院', '计', '算', '机', '工', '程', '学', '位', '以', '及', '色', '雷', '斯', '德', '谟', '克', '利', '特', '大', '学', '电', '子', '和', '计', '算', '机', '工', '程', '学', '位', '，', ' ', '都', '从', '事', '过', '软', '件', '开', '发', '工', '作', '，', ' ', '且', '目', '前', '均', '为', '教', '授', '计', '算', '机', '相', '关', '课', '程', '的', '高', '中', '教', '师', '。', '他', '们', '写', '了', '很', '多', '关', '于', '算', '法', '和', '计', '算', '思', '维', '方', '面', '的', '书', '，', ' ', '涉', '及', 'P', 'y', 't', 'h', 'o', 'n', '、', 'C', '#', '、', 'J', 'a', 'v', 'a', '、', 'C', '+', '+', '、', 'P', 'H', 'P', ' ', '和', 'V', ' ', 'i', ' ', 's', ' ', 'u', ' ', 'a', ' ', 'l', ' ', 'B', ' ', 'a', ' ', 's', ' ', 'i', ' ', 'c', ' ', '等', '语', '言', '。']\n"
     ]
    }
   ],
   "source": [
    "# 截取一部分数据便于更快训练\n",
    "dataset.train_data = dataset.train_data[:1000]\n",
    "dataset.test_data = dataset.test_data[:200]\n",
    "\n",
    "dataset.build_vocab(min_freq=0)\n",
    "dataset.convert_tokens_to_ids()\n",
    "print(dataset.train_data[0]['token_ids'])\n",
    "print([dataset.id2token[token_id] for token_id in \\\n",
    "       dataset.train_data[0]['token_ids']])\n",
    "\n",
    "def collect_data(data_split):\n",
    "    X, Y = [], []\n",
    "    for data in data_split:\n",
    "        assert len(data['token_ids']) == len(data['tags'])\n",
    "        X.append(data['token_ids'])\n",
    "        Y.append(data['tags'])\n",
    "    return X, Y\n",
    "\n",
    "train_X, train_Y = collect_data(dataset.train_data)\n",
    "test_X, test_Y = collect_data(dataset.test_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "4b214ece",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "class HMM:\n",
    "    def __init__(self, n_tags, n_tokens):\n",
    "        self.n_tags = n_tags\n",
    "        self.n_tokens = n_tokens\n",
    "    \n",
    "    # 使用最大似然估计计算模型参数\n",
    "    def fit(self, X, Y):\n",
    "        Y0_cnt = np.zeros(self.n_tags)\n",
    "        YY_cnt = np.zeros((self.n_tags, self.n_tags))\n",
    "        YX_cnt = np.zeros((self.n_tags, self.n_tokens))\n",
    "        for x, y in zip(X, Y):\n",
    "            Y0_cnt[y[0]] += 1\n",
    "            last_y = y[0]\n",
    "            for i in range(1, len(y)):\n",
    "                YY_cnt[last_y, y[i]] += 1\n",
    "                last_y = y[i]\n",
    "            for xi, yi in zip(x, y):\n",
    "                YX_cnt[yi, xi] += 1\n",
    "        self.init_prob = Y0_cnt / Y0_cnt.sum()\n",
    "        self.transition_prob = YY_cnt\n",
    "        self.emission_prob = YX_cnt\n",
    "        for i in range(self.n_tags):\n",
    "            # 为了避免训练集过小时除0\n",
    "            yy_sum = YY_cnt[i].sum()\n",
    "            if yy_sum > 0:\n",
    "                self.transition_prob[i] = YY_cnt[i] / yy_sum\n",
    "            yx_sum = YX_cnt[i].sum()\n",
    "            if yx_sum > 0:\n",
    "                self.emission_prob[i] = YX_cnt[i] / yx_sum\n",
    "    \n",
    "    # 已知模型参数的条件下，使用维特比算法解码得到最优标签序列\n",
    "    def viterbi(self, x):\n",
    "        assert hasattr(self, 'init_prob') and hasattr(self,\\\n",
    "            'transition_prob') and hasattr(self, 'emission_prob')\n",
    "        Pi = np.zeros((len(x), self.n_tags))\n",
    "        Y = np.zeros((len(x), self.n_tags), dtype=np.int32)\n",
    "        # 初始化\n",
    "        for i in range(self.n_tags):\n",
    "            Pi[0, i] = self.init_prob[i] * self.emission_prob[i, x[0]]\n",
    "            Y[0, i] = -1\n",
    "        for t in range(1, len(x)):\n",
    "            for i in range(self.n_tags):\n",
    "                tmp = []\n",
    "                for j in range(self.n_tags):\n",
    "                    tmp.append(self.transition_prob[j, i] * Pi[t-1, j])\n",
    "                best_j = np.argmax(tmp)\n",
    "                # 维特比算法递推公式\n",
    "                Pi[t, i] = self.emission_prob[i, x[t]] * tmp[best_j]\n",
    "                Y[t, i] = best_j\n",
    "        y = [np.argmax(Pi[-1])]\n",
    "        for t in range(len(x)-1, 0, -1):\n",
    "            y.append(Y[t, y[-1]])\n",
    "        return np.max(Pi[len(x)-1]), y[::-1]\n",
    "    \n",
    "    def decode(self, X):\n",
    "        Y = []\n",
    "        for x in X:\n",
    "            _, y = self.viterbi(x)\n",
    "            Y.append(y)\n",
    "        return Y\n",
    "\n",
    "hmm = HMM(len(dataset.label2id), len(dataset.token2id))\n",
    "hmm.fit(train_X, train_Y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "9ed35bf6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.45762149610217284, recall = 0.3289222699093944, f1 = 0.38274259554692375\n",
      "precision = 0.4189636163175303, recall = 0.23270055113288426, f1 = 0.2992125984251969\n"
     ]
    }
   ],
   "source": [
    "def extract_entity(labels):\n",
    "    entity_list = []\n",
    "    entity_start = -1\n",
    "    entity_length = 0\n",
    "    entity_type = None\n",
    "    for token_index, label in enumerate(labels):\n",
    "        if label.startswith('B'):\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的B，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 记录新实体\n",
    "            entity_start = token_index\n",
    "            entity_length = 1\n",
    "            entity_type = label.split('-')[1]\n",
    "        elif label.startswith('I'):\n",
    "            if entity_start != -1:\n",
    "                # 上一个实体未关闭，遇到了一个新的I\n",
    "                if entity_type == label.split('-')[1]:\n",
    "                    # 若上一个实体与当前类型相同，长度+1\n",
    "                    entity_length += 1\n",
    "                else:\n",
    "                    # 若上一个实体与当前类型不同，\n",
    "                    # 将上一个实体加入列表，重置实体\n",
    "                    entity_list.append((entity_start, entity_length,\\\n",
    "                        entity_type))\n",
    "                    entity_start = -1\n",
    "                    entity_length = 0\n",
    "                    entity_type = None\n",
    "        else:\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的O，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 重置实体\n",
    "            entity_start = -1\n",
    "            entity_length = 0\n",
    "            entity_type = None\n",
    "    if entity_start != -1:\n",
    "        # 将上一个实体加入列表\n",
    "        entity_list.append((entity_start, entity_length, entity_type))\n",
    "    return entity_list\n",
    "\n",
    "def compute_metric(Y, P):\n",
    "    true_entity_set = set()\n",
    "    pred_entity_set = set()\n",
    "    for sent_no, labels in enumerate(Y):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            true_entity_set.add((sent_no, ent))\n",
    "    for sent_no, labels in enumerate(P):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            pred_entity_set.add((sent_no, ent))\n",
    "    if len(true_entity_set) > 0:\n",
    "        recall = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(true_entity_set)\n",
    "    else:\n",
    "        recall = 0\n",
    "    if len(pred_entity_set) > 0:\n",
    "        precision = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(pred_entity_set)\n",
    "    else:\n",
    "        precision = 0\n",
    "    if precision > 0 and recall > 0:\n",
    "        f1 = 2 * precision * recall / (precision + recall)\n",
    "    else:\n",
    "        f1 = 0\n",
    "    return precision, recall, f1\n",
    "\n",
    "train_P = hmm.decode(train_X)\n",
    "p, r, f = compute_metric(train_Y, train_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "test_P = hmm.decode(test_X)\n",
    "p, r, f = compute_metric(test_Y, test_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "12314277",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "代码修改自GitHub项目kmkurn/pytorch-crf\n",
    "（Copyright (c) 2019, Kemal Kurniawan, MIT License（见附录））\n",
    "\"\"\"\n",
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "class CRFLayer(nn.Module):\n",
    "    def __init__(self, n_tags, n_features):\n",
    "        super().__init__()\n",
    "        self.n_tags = n_tags\n",
    "        self.n_features = n_features\n",
    "        # 定义模型参数\n",
    "        self.transitions = nn.Parameter(torch.empty(\\\n",
    "            n_tags, n_tags))\n",
    "        self.emission_weight = nn.Parameter(torch.empty(\\\n",
    "            n_features, n_tags))\n",
    "        self.start_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.end_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.reset_parameters()\n",
    "        \n",
    "    # 使用（-0.1,0.1）之间的均匀分布初始化参数\n",
    "    def reset_parameters(self):\n",
    "        nn.init.uniform_(self.transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.emission_weight, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.start_transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.end_transitions, -0.1, 0.1)\n",
    "    \n",
    "    # 使用动态规划计算得分\n",
    "    def compute_score(self, emissions, tags, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions[tags[0]] + \\\n",
    "            emissions[0, torch.arange(batch_size), tags[0]]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            score += self.transitions[tags[i-1], tags[i]] * masks[i]\n",
    "            score += emissions[i, torch.arange(batch_size),\\\n",
    "                tags[i]] * masks[i]\n",
    "        \n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        last_tags = tags[seq_ends, torch.arange(batch_size)]\n",
    "        score += self.end_transitions[last_tags]\n",
    "        return score\n",
    "    \n",
    "    # 计算配分函数\n",
    "    def computer_normalizer(self, emissions, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        # batch_size * n_tags, [起始分数 + y_0为某标签的发射分数 ...]\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            # batch_size * n_tags * 1 [y_{i-1}为某tag的总分]\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            # batch_size * 1 * n_tags [y_i为某标签的发射分数]\n",
    "            broadcast_emissions = emissions[i].unsqueeze(1)\n",
    "            # batch_size * n_tags * n_tags [任意y_{i-1}到y_i的总分]\n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emissions\n",
    "            # batch_size * n_tags [对y_{i-1}求和]\n",
    "            next_score = torch.logsumexp(next_score, dim=1)\n",
    "            # masks为True则更新，否则保留\n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score, score)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        return torch.logsumexp(score, dim=1)\n",
    "    \n",
    "    def forward(self, features, tags, masks):\n",
    "        \"\"\"\n",
    "        features: seq_len * batch_size * n_features\n",
    "        tags/masks: seq_len * batch_size\n",
    "        \"\"\"\n",
    "        _, batch_size, _ = features.size()\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        score = self.compute_score(emissions, tags, masks)\n",
    "        partition = self.computer_normalizer(emissions, masks)\n",
    "        \n",
    "        likelihood = score - partition\n",
    "        return likelihood.sum() / batch_size\n",
    "    \n",
    "    def decode(self, features, masks):\n",
    "        # 与computer_normalizer类似，sum变为max\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        history = []\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            broadcast_emission = emissions[i].unsqueeze(1)\n",
    "            \n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emission\n",
    "            next_score, indices = next_score.max(dim=1)\n",
    "            \n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score,\\\n",
    "                score)\n",
    "            history.append(indices)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        best_tags_list = []\n",
    "        \n",
    "        for idx in range(batch_size):\n",
    "            _, best_last_tag = score[idx].max(dim=0)\n",
    "            best_tags = [best_last_tag.item()]\n",
    "            \n",
    "            for hist in reversed(history[:seq_ends[idx]]):\n",
    "                best_last_tag = hist[idx][best_tags[-1]]\n",
    "                best_tags.append(best_last_tag.item())\n",
    "                \n",
    "            best_tags.reverse()\n",
    "            best_tags_list.append(best_tags)\n",
    "            \n",
    "        return best_tags_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "83f51bc9",
   "metadata": {},
   "outputs": [],
   "source": [
    "class CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size)\n",
    "        \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(embed, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        # 调用CRFLayer进行解码\n",
    "        return self.crf.decode(embed, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "77e59061",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=51.18: 100%|█| 20/20 [02:39<00:00,  7.95s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torch.optim import SGD, Adam\n",
    "from copy import deepcopy\n",
    "\n",
    "# 将数据适配为PyTorch数据集\n",
    "class SeqLabelDataset(Dataset):\n",
    "    def __init__(self, tokens, labels):\n",
    "        self.tokens = deepcopy(tokens)\n",
    "        self.labels = deepcopy(labels)\n",
    "        \n",
    "    def __getitem__(self, idx):\n",
    "        return self.tokens[idx], self.labels[idx]\n",
    "    \n",
    "    def __len__(self):\n",
    "        return len(self.labels)\n",
    "    \n",
    "    # 将每个批次转化为PyTorch张量\n",
    "    @classmethod\n",
    "    def collate_batch(cls, inputs):\n",
    "        input_ids, labels, masks = [], [], []\n",
    "        max_len = -1\n",
    "        for ids, tags in inputs:\n",
    "            input_ids.append(ids)\n",
    "            labels.append(tags)\n",
    "            masks.append([1] * len(tags))\n",
    "            max_len = max(max_len, len(ids))\n",
    "        for ids, tags, msks in zip(input_ids, labels, masks):\n",
    "            pad_len = max_len - len(ids)\n",
    "            ids.extend([0] * pad_len)\n",
    "            tags.extend([0] * pad_len)\n",
    "            msks.extend([0] * pad_len)\n",
    "        input_ids = torch.tensor(np.array(input_ids),\\\n",
    "            dtype=torch.long)\n",
    "        labels = torch.tensor(np.array(labels), dtype=torch.long)\n",
    "        masks = torch.tensor(np.array(masks), dtype=torch.uint8)\n",
    "        return {'input_ids': input_ids, 'masks': masks,\\\n",
    "                'labels': labels}\n",
    "    \n",
    "# 准备数据\n",
    "train_dataset = SeqLabelDataset(train_X, train_Y)\n",
    "test_dataset = SeqLabelDataset(test_X, test_Y)\n",
    "\n",
    "from tqdm import tqdm, trange\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def train(model, batch_size, epochs, learning_rate):\n",
    "    train_dataloader = DataLoader(train_dataset,\n",
    "        batch_size=batch_size, shuffle=True, \n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    optimizer = Adam(model.parameters(), lr=learning_rate)\n",
    "    model.zero_grad()\n",
    "    tr_step = []\n",
    "    tr_loss = []\n",
    "    global_step = 0\n",
    "    \n",
    "    with trange(epochs, desc='epoch', ncols=60) as pbar:\n",
    "        for epoch in pbar:\n",
    "            model.train()\n",
    "            for step, batch in enumerate(train_dataloader):\n",
    "                global_step += 1\n",
    "                loss = model(**batch)\n",
    "                loss.backward()\n",
    "                optimizer.step()\n",
    "                model.zero_grad()\n",
    "                pbar.set_description(f'epoch-{epoch}, '+\\\n",
    "                    f'loss={loss.item():.2f}')\n",
    "                \n",
    "                if epoch > 0:\n",
    "                    tr_step.append(global_step)\n",
    "                    tr_loss.append(loss.item())\n",
    "\n",
    "    # 打印损失曲线\n",
    "    plt.plot(tr_step, tr_loss, label='train loss')\n",
    "    plt.xlabel('training steps')\n",
    "    plt.ylabel('loss')\n",
    "    plt.legend()\n",
    "    plt.show()\n",
    "                \n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_tags = len(dataset.label2id)\n",
    "crf = CRF(vocab_size, hidden_size, n_tags)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "train(crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f3966c50",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.5459785171600734, recall = 0.24845016690510252, f1 = 0.34149938549774683\n",
      "precision = 0.4881450488145049, recall = 0.21432945499081446, f1 = 0.29787234042553196\n"
     ]
    }
   ],
   "source": [
    "# 验证效果\n",
    "\n",
    "def evaluate(X, Y, model, batch_size):\n",
    "    dataset = SeqLabelDataset(X, Y)\n",
    "    dataloader = DataLoader(dataset, batch_size=batch_size,\\\n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        P = []\n",
    "        for batch in dataloader:\n",
    "            preds = model.decode(batch['input_ids'], batch['masks'])\n",
    "            P.extend(preds)\n",
    "\n",
    "    p, r, f = compute_metric(Y, P)\n",
    "    print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "    \n",
    "evaluate(train_X, train_Y, crf, batch_size)\n",
    "evaluate(test_X, test_Y, crf, batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2ce43a70",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "# 定义模型，将长短期记忆的输出输入给条件随机场，\n",
    "# 利用条件随机场计算损失和解码\n",
    "class LSTM_CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, num_layers,\\\n",
    "                 dropout, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.lstm = nn.LSTM(input_size=hidden_size,\n",
    "                            hidden_size=hidden_size,\n",
    "                            num_layers=num_layers,\n",
    "                            batch_first=False,\n",
    "                            dropout=dropout,\n",
    "                            bidirectional=True)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size * 2)\n",
    "    \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(hidden_states, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 调用条件随机场进行解码\n",
    "        return self.crf.decode(hidden_states, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "aebc33ef",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=29.00: 100%|█| 20/20 [03:21<00:00, 10.09s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABteUlEQVR4nO3dd3zV1f3H8dfNzd6LLAgQ9kgYAjIVVIYo4Kq4lVbbWifuWjvQWlHrLDha60+to9hWwK2AyJK9w14BQkgIhOyd3O/vj5v7TW42IYvL+/l43EfJ/X7v954jFd6e8znnWAzDMBARERFxUW5t3QARERGRlqSwIyIiIi5NYUdERERcmsKOiIiIuDSFHREREXFpCjsiIiLi0hR2RERExKW5t3UD2gObzcbx48cJCAjAYrG0dXNERESkEQzDIDc3l5iYGNzc6h6/UdgBjh8/TmxsbFs3Q0RERJogOTmZTp061XldYQcICAgA7P+wAgMD27g1IiIi0hg5OTnExsaaf4/XRWEHzKmrwMBAhR0REZFzTEMlKCpQFhEREZemsCMiIiIuTWFHREREXJpqdkRExOWVl5dTWlra1s2QM+Th4YHVaj3r5yjsiIiIyzIMg7S0NLKystq6KdJEwcHBREVFndU+eAo7IiLishxBJyIiAl9fX20cew4xDIOCggLS09MBiI6ObvKzFHZERMQllZeXm0EnLCysrZsjTeDj4wNAeno6ERERTZ7SUoGyiIi4JEeNjq+vbxu3RM6G4/fvbGquFHZERMSlaerq3NYcv38KOyIiIuLSFHZERETEpSnsiIiIuLiuXbvy2muvtfkz2opWY7WiwpJyfDzPfnMkERFxbePGjWPQoEHNFi42bNiAn59fszzrXKSRnVby0dojxM/6nh/3pLd1U0RExAUYhkFZWVmj7u3QocN5vSpNYaeVbDmaRbnNYEtyVls3RUTkvGUYBgUlZW3yMgyjUW2cMWMGy5cv5/XXX8disWCxWDh8+DDLli3DYrHw/fffM3ToULy8vFi5ciUHDx7kqquuIjIyEn9/f4YNG8aSJUucnll9CspisfDPf/6Ta665Bl9fX3r27MkXX3xxRv8sjx49ylVXXYW/vz+BgYFMnz6dEydOmNe3bdvGJZdcQkBAAIGBgQwZMoSNGzcCcOTIEaZOnUpISAh+fn7079+fb7755oy+/0xoGquVlJTbAMgvblwKFxGR5ldYWk6/P37fJt+965lJ+Ho2/Nfu66+/zr59+4iPj+eZZ54B7CMzhw8fBuDxxx/npZdeolu3bgQHB3Ps2DGuuOIKnn32Wby9vfnggw+YOnUqe/fupXPnznV+z9NPP82LL77IX//6V+bMmcMtt9zCkSNHCA0NbbCNhmFw9dVX4+fnx/LlyykrK+Oee+7hhhtuYNmyZQDccsstDB48mLfeegur1crWrVvx8PAA4N5776WkpIQVK1bg5+fHrl278Pf3b/B7m0php5WUlJUDUFCisCMiInULCgrC09MTX19foqKialx/5plnmDBhgvlzWFgYAwcONH9+9tlnWbBgAV988QX33Xdfnd8zY8YMbrrpJgCee+455syZw/r167n88ssbbOOSJUvYvn07SUlJxMbGAvDhhx/Sv39/NmzYwLBhwzh69CiPPfYYffr0AaBnz57m548ePcp1111HQkICAN26dWvwO8+Gwk4rKSlzjOyUt3FLRETOXz4eVnY9M6nNvrs5DB061Onn/Px8nn76ab766iuOHz9OWVkZhYWFHD16tN7nDBgwwPy1n58fAQEB5jlUDdm9ezexsbFm0AHo168fwcHB7N69m2HDhvHwww9z11138eGHHzJ+/Hiuv/56unfvDsADDzzAb37zGxYtWsT48eO57rrrnNrT3FSz00qKyzSNJSLS1iwWC76e7m3yaq6dnKuvqnrsscf47LPP+Mtf/sLKlSvZunUrCQkJlJSU1Pscx5RS1X82NputUW0wDKPW/lR9f9asWezcuZMrr7ySpUuX0q9fPxYsWADAXXfdxaFDh7jttttITExk6NChzJkzp1Hf3RQKO63EHNnRNJaIiDTA09OT8vLGzQSsXLmSGTNmcM0115CQkEBUVJRZ39NS+vXrx9GjR0lOTjbf27VrF9nZ2fTt29d8r1evXjz00EMsWrSIa6+9lvfee8+8Fhsby9133838+fN55JFHeOedd1qsvQo7rcRRoFxQomksERGpX9euXVm3bh2HDx/m1KlT9Y649OjRg/nz57N161a2bdvGzTff3OgRmqYaP348AwYM4JZbbmHz5s2sX7+e22+/nbFjxzJ06FAKCwu57777WLZsGUeOHOGnn35iw4YNZhCaOXMm33//PUlJSWzevJmlS5c6haTmprDTShwjO3maxhIRkQY8+uij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G/e2/PLvvvvO6ef33nuPiIgINm3axMUXX2y+7+XlRVRUVK3PyM7O5t133+XDDz9k/PjxAHz00UfExsayZMkSJk2a1HIdaIBjCgsaV7PjGNk5maewIyIi0lzaVc1OdnY2AKGhoU7vL1u2jIiICHr16sUvf/lL0tPTzWubNm2itLSUiRMnmu/FxMQQHx/P6tWra/2e4uJicnJynF4tofhMw05FzY5GdkRERJpPuwk7hmHw8MMPM2bMGOLj4833J0+ezMcff8zSpUt5+eWX2bBhA5deeinFxfZAkJaWhqenJyEhIU7Pi4yMJC0trdbvmj17NkFBQeYrNja2RfrkGNnxsFpwc7M0eL8j7OQWlWkXZRERkWbSptNYVd13331s376dVatWOb1/ww03mL+Oj49n6NChdOnSha+//pprr722zucZhoHFUnvAePLJJ3n44YfNn3Nyclok8JzJsnOAQG93PN3dKCmzcTK3mNhQ32Zvk4iIyPmmXYzs3H///XzxxRf8+OOPdOrUqd57o6Oj6dKlC/v37wcgKiqKkpISMjMzne5LT08nMjKy1md4eXkRGBjo9GoJZ7ISC+y7KKtuR0REpHm1adgxDIP77ruP+fPns3TpUuLi4hr8TEZGBsnJyURHRwMwZMgQPDw8WLx4sXlPamoqO3bsYNSoUS3W9sY4kz12HBxTWek5CjsiIiLNoU2nse69914++eQTPv/8cwICAswam6CgIHx8fMjLy2PWrFlcd911REdHc/jwYX73u98RHh7ONddcY95755138sgjjxAWFkZoaCiPPvooCQkJ5uqstlLcyENAq4oI0MiOiIhIc2rTsPPWW28BMG7cOKf333vvPWbMmIHVaiUxMZF//etfZGVlER0dzSWXXMKnn35KQECAef+rr76Ku7s706dPp7CwkMsuu4z3338fq7XxIaMlnM3IjlZkiYiINI82DTuGYdR73cfHh++//77B53h7ezNnzhzmzJnTXE1rFmbNTiMLlEFhR0REpLm1iwJlV6WRHRERkbansNOCmhR2tBpLRESkWSnstKCSirO5vJoyspOjw0BFRESag8JOCzrTTQWhymGgecUN1jSJiIhIwxR2WlBTprHC/T0BKC03yC4sbZF2iYiInE8UdlpQcRPCjpe7lSAfD0BFyiIiIs1BYacFFTdhGgu0IktERKQ5Key0oKZMY4FW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tbh5zZ49G4vFwsyZM833XK2/KSkp3HrrrYSFheHr68ugQYPYtGmTed2V+ltWVsbvf/974uLi8PHxoVu3bjzzzDPYbDbznnO1vytWrGDq1KnExMRgsVhYuHCh0/XG9Ku4uJj777+f8PBw/Pz8mDZtGseOHWvFXjReff0tLS3liSeeICEhAT8/P2JiYrj99ts5fvy40zNcpb/V/frXv8ZisfDaa685ve9q/d29ezfTpk0jKCiIgIAARowYwdGjR83rrdVfhR0XsHz5cu69917Wrl3L4sWLKSsrY+LEieTn55v3vPjii7zyyivMnTuXDRs2EBUVxYQJE8xzwc5VGzZs4B//+AcDBgxwet+V+puZmcno0aPx8PDg22+/ZdeuXbz88ssEBweb97hSf1944QXefvtt5s6dy+7du3nxxRf561//ypw5c8x7ztX+5ufnM3DgQObOnVvr9cb0a+bMmSxYsIB58+axatUq8vLymDJlCuXl5a3VjUarr78FBQVs3ryZP/zhD2zevJn58+ezb98+pk2b5nSfq/S3qoULF7Ju3TpiYmJqXHOl/h48eJAxY8bQp08fli1bxrZt2/jDH/6At7e3eU+r9dcQl5Oenm4AxvLlyw3DMAybzWZERUUZzz//vHlPUVGRERQUZLz99ttt1cyzlpuba/Ts2dNYvHixMXbsWOPBBx80DMP1+vvEE08YY8aMqfO6q/X3yiuvNH7xi184vXfttdcat956q2EYrtNfwFiwYIH5c2P6lZWVZXh4eBjz5s0z70lJSTHc3NyM7777rtXa3hTV+1ub9evXG4Bx5MgRwzBcs7/Hjh0zOnbsaOzYscPo0qWL8eqrr5rXXK2/N9xwg/nvbW1as78a2XFB2dnZAISGhgKQlJREWloaEydONO/x8vJi7NixrF69uk3a2BzuvfderrzySsaPH+/0vqv194svvmDo0KFcf/31REREMHjwYN555x3zuqv1d8yYMfzwww/s27cPgG3btrFq1SquuOIKwPX669CYfm3atInS0lKne2JiYoiPjz+n++6QnZ2NxWIxRy1drb82m43bbruNxx57jP79+9e47kr9tdlsfP311/Tq1YtJkyYRERHB8OHDnaa6WrO/CjsuxjAMHn74YcaMGUN8fDwAaWlpAERGRjrdGxkZaV4718ybN4/Nmzcze/bsGtdcrb+HDh3irbfe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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 准备优化器和模型\n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_layers = 1\n",
    "dropout = 0\n",
    "n_tags = len(dataset.label2id)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "\n",
    "lstm_crf = LSTM_CRF(vocab_size, hidden_size, n_layers, dropout, n_tags)\n",
    "train(lstm_crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "69618be9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.6119703062171358, recall = 0.4717453505007153, f1 = 0.5327857816076479\n",
      "precision = 0.5446280991735537, recall = 0.4035517452541335, f1 = 0.4635947942314457\n"
     ]
    }
   ],
   "source": [
    "evaluate(train_X, train_Y, lstm_crf, batch_size)\n",
    "evaluate(test_X, test_Y, lstm_crf, batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13ece55e",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
